/*
 * Copyright (C) Michael Martin & Antonio Carrozzini (TBJD)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package org.tbjd.client.guis.gantt.items;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.tbjd.client.beans.Resource;
import org.tbjd.client.beans.Task;
import org.tbjd.client.constants.GanttConstants;
import org.tbjd.client.constants.NotificationsConstants;
import org.tbjd.client.constants.ZoomConstants;
import org.tbjd.client.dndControllers.gantt.taskItem.GanttTaskItemResourceDropController;
import org.tbjd.client.exceptions.NoUpperDecisionmakerFoundException;
import org.tbjd.client.exceptions.NoUpperMediatorFoundException;
import org.tbjd.client.exceptions.TBJDException;
import org.tbjd.client.images.ganttTaskItem.CapsuleImageBundle;
import org.tbjd.client.interfaces.Builder;
import org.tbjd.client.interfaces.Decisionmaker;

import com.allen_sauer.gwt.dnd.client.PickupDragController;
import com.allen_sauer.gwt.dnd.client.drop.SimpleDropController;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.FocusPanel;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * <p>
 * Represents a {@link org.tbjd.client.beans.Task Task} on the {@link org.tbjd.client.guis.gantt.GanttGui GanttGui}
 * </p>
 * <p>
 * Consist of a container with several pieces :
 * <li>a left zone to display the rounded left ending of the item</li>
 * <li>a center zone to display progression and eventually the name of the task</li>
 * <li>a right handle to change the duration of the task</li>
 * <li>a progression slider to change the progression of the task (below)</li>
 * <li>a resource area to display associated Resources (on the extreme right)</li>
 * </p>
 * 
 * @author Michael Martin
 * @author Antonio Carrozzini
 * @version 1.0
 * 
 */
public class GanttTaskItem extends AbstractGanttItem implements Builder, Decisionmaker {

	/**
	 * The list of {@link org.tbjd.client.beans.Resource Resources} associated with the Task
	 */
	private List<Resource> resources = new ArrayList<Resource>();
 
	private HorizontalPanel container = null;
	private AbsolutePanel capsuleCenterContainer = null;
	private AbsolutePanel sliderPanel = null;
	private AbsolutePanel rightHandlePanel = null;
	private FocusPanel leftHandlePanel = null;
	private FocusPanel sliderPanelHandle = null;
	private FocusPanel capsuleCenterForeground = null;
	private SimplePanel capsuleCenterBackground = null;
	private FocusPanel rightHandle = null;
	private HorizontalPanel resourcesPanel = null;

	private PickupDragController resourceDragController = null;
	private SimpleDropController resourceDropController = null;

	private CapsuleImageBundle capsuleImageBundle = (CapsuleImageBundle) GWT.create(CapsuleImageBundle.class);

	private List<Integer> predecessors = null;
	private List<Integer> successors = null;
	private int startupDurationWidth;
	private VerticalPanel taskContainer = null;
	private Label label = null;

	/**
	 * The width in pixels of the GanttTaskItem
	 */
	private int width;

	public GanttTaskItem(Task task) {
		setBean(task);
		this.resources = task.getResources();
		build();
		initWidget(this.container);
	}

	public void build() {
		// taskCapsule id & yPosition
		setId(((Task) getBean()).getId());
		setYCoordinate(((Task) getBean()).getLevel());

		// instantiation
		this.predecessors = new ArrayList<Integer>();
		this.successors = new ArrayList<Integer>();

		this.container = new HorizontalPanel();
		this.taskContainer = new VerticalPanel();
		this.leftHandlePanel = new FocusPanel();
		this.rightHandle = new FocusPanel();
		this.rightHandlePanel = new AbsolutePanel();
		this.capsuleCenterContainer = new AbsolutePanel();
		this.capsuleCenterBackground = new SimplePanel();
		this.capsuleCenterForeground = new FocusPanel();
		setHandle(this.capsuleCenterForeground);
		HorizontalPanel capsuleContainer = new HorizontalPanel();
		this.sliderPanel = new AbsolutePanel();
		this.sliderPanelHandle = new FocusPanel();
		this.resourcesPanel = new HorizontalPanel();

		// styles
		this.container.setStyleName("ganttTaskItem");
		this.taskContainer.setStyleName("ganttTaskItem_taskContainer");
		this.leftHandlePanel.setStyleName("ganttTaskItem_leftHandlePanel");
		this.capsuleCenterContainer.setStyleName("ganttTaskItem_capsuleCenterContainer");
		this.capsuleCenterBackground.setStyleName("ganttTaskItem_capsuleCenterBackground");
		this.capsuleCenterForeground.setStyleName("ganttTaskItem_capsuleCenterForeground");
		this.rightHandlePanel.setStyleName("ganttTaskItem_rightHandlePanel");
		this.rightHandle.setStyleName("ganttTaskItem_rightHandle");
		this.sliderPanel.setStyleName("ganttTaskItem_sliderPanel");
		this.sliderPanelHandle.setStyleName("ganttTaskItem_ganttTaskItem_sliderPanelHandle");

		// center zone
		this.capsuleCenterBackground.setWidth(((Task) getBean()).getProgress() + "%");
		this.capsuleCenterBackground.setHeight(GanttConstants.DRAGGABLEGANTTITEM_HEIGHT + "px");
		this.capsuleCenterContainer.add(this.capsuleCenterBackground);
		this.capsuleCenterForeground.setHeight(GanttConstants.DRAGGABLEGANTTITEM_HEIGHT + "px");
		this.label = new Label(((Task) getBean()).getName());
		this.capsuleCenterForeground.add(this.label);
		this.capsuleCenterContainer.add(this.capsuleCenterForeground, 0, 0);
		this.capsuleCenterContainer.setHeight(GanttConstants.DRAGGABLEGANTTITEM_HEIGHT + "px");

		// handles panel (duration handle)
		// this title is used by the GanttTaskItemDurationDragController to know which task is related to the dragged handle
		this.leftHandlePanel.setSize(GanttConstants.GANTTASKTITEM_HANDLEWIDTH + "px", GanttConstants.GANTTASKTITEM_HANDLEHEIGHT + "px");
		this.leftHandlePanel.add(this.capsuleImageBundle.leftHandle_8x16().createImage());
		this.rightHandlePanel.setSize(GanttConstants.GANTTASKTITEM_HANDLEWIDTH + "px", GanttConstants.GANTTASKTITEM_HANDLEHEIGHT + "px");
		this.rightHandle.setTitle(((Task) getBean()).getId() + "");
		this.rightHandle.add(this.capsuleImageBundle.rightHandle_8x16().createImage());
		this.rightHandlePanel.add(this.rightHandle);

		// capsuleContainer contains capsuleCenterContainer + handles
		capsuleContainer.setStyleName("ganttTaskItem_capsuleContainer");
		capsuleContainer.add(this.leftHandlePanel);
		capsuleContainer.add(this.capsuleCenterContainer);
		capsuleContainer.add(this.rightHandlePanel);
		capsuleContainer.setHeight(GanttConstants.DRAGGABLEGANTTITEM_HEIGHT + "px");
		this.taskContainer.add(capsuleContainer);

		// slider panel
		this.sliderPanelHandle.add(this.capsuleImageBundle.progressBulletHandle_10x9().createImage());
		this.sliderPanel.add(this.sliderPanelHandle);
		this.sliderPanel.setHeight(GanttConstants.SLIDERPANEL_HEIGHT + "px");
		this.taskContainer.add(sliderPanel);

		// resources
		this.resourcesPanel.setHeight(GanttConstants.DRAGGABLEGANTTITEM_HEIGHT + "px");
		this.resourceDropController = new GanttTaskItemResourceDropController(RootPanel.get());
		((GanttTaskItemResourceDropController) this.resourceDropController).setDecisionmaker(this.resourcesPanel);
		this.resourceDragController = new PickupDragController(RootPanel.get(), true);
		this.resourceDragController.setBehaviorDragStartSensitivity(20);
		this.resourceDragController.registerDropController(this.resourceDropController);
		displayResources();

		this.container.add(this.taskContainer);
		this.container.add(this.resourcesPanel);

		setComputedWidth();

		// because at startup, backgroundProgress has no width, we must
		// position the sliderPanelHandle manually.
		// To do so, we must find the width in pixels of the backgroundProgress
		// and apply it to the sliderPanelHandle.
		int progressPanelWidth = ((Task) getBean()).getProgress();
		int centerCapsulePanel = this.startupDurationWidth;
		this.sliderPanel.setWidgetPosition(this.sliderPanelHandle, progressPanelWidth * centerCapsulePanel / 100, 0);

	}

	public void decide(Object action) {
		if (((Map<Integer, ?>) action).containsKey(NotificationsConstants.TASK_UPDATED)) {
			System.out.println("GanttTaskItem.decide() TASK_UPDATED");

			// create a map with the updated task
			Map<Integer, Object> map = new HashMap<Integer, Object>();
			map.put(NotificationsConstants.TASK_UPDATED, this.getBean());

			// and notify chief
			try {
				delegate(map);
			} catch (NoUpperDecisionmakerFoundException e) {
				e.printStackTrace();
			} catch (NoUpperMediatorFoundException e) {
				e.printStackTrace();
			}
		}

		if (((Map<Integer, ?>) action).containsKey(NotificationsConstants.TASK_PROGRESS_DRAGGING)) {
			System.out.println("GanttTaskItem.decide() TASK_PROGRESS_DRAGGING");

			// update task infos
			handle(action);
		}

		if (((Map<Integer, ?>) action).containsKey(NotificationsConstants.DROP_REMOVE_RESOURCEFROMTASK_resource)) {
			System.out.println("GanttTaskItem.decide() DROP_REMOVE_RESOURCEFROMTASK_resource");

			// update items and beans
			handle(action);

			// notify the chief with updated beans lists (tasks & resources)
			Resource resource = (Resource) ((Map<Integer, ?>) action).get(NotificationsConstants.DROP_REMOVE_RESOURCEFROMTASK_resource);
			Map<Integer, Object> map = new HashMap<Integer, Object>();
			map.put(NotificationsConstants.DROP_REMOVE_RESOURCEFROMTASK_task, (Task) getBean());
			map.put(NotificationsConstants.DROP_REMOVE_RESOURCEFROMTASK_resource, resource);

			// and notify chief
			try {
				delegate(map);
			} catch (NoUpperDecisionmakerFoundException e) {
				e.printStackTrace();
			} catch (NoUpperMediatorFoundException e) {
				e.printStackTrace();
			}
		}

		if (((Map<Integer, ?>) action).containsKey(NotificationsConstants.TASK_RESOURCES_DRAGGING)) {
			System.out.println("GanttTaskItem.decide() TASK_RESOURCES_DRAGGING");

			// update task infos
			handle(action);

			// create a map with the updated task
			Map<Integer, Object> map = new HashMap<Integer, Object>();
			map.put(NotificationsConstants.TASK_RESOURCES_DRAGGING, this);

			// and notify chief
			try {
				delegate(map);
			} catch (NoUpperDecisionmakerFoundException e) {
				e.printStackTrace();
			} catch (NoUpperMediatorFoundException e) {
				e.printStackTrace();
			}
		}

		if (((Map<Integer, ?>) action).containsKey(NotificationsConstants.TASK_DURATION_DRAGGING)) {
			System.out.println("GanttTaskItem.decide() TASK_DURATION_DRAGGING");

			// update task infos
			handle(action);

			// delegate, thus GanttItemContainerGui can move children if BorderNotifier tells to
			try {
				delegate(action);
			} catch (NoUpperDecisionmakerFoundException e) {
				e.printStackTrace();
			} catch (NoUpperMediatorFoundException e) {
				e.printStackTrace();
			}
		}
	}

	public void delegate(Object action) throws NoUpperDecisionmakerFoundException, NoUpperMediatorFoundException {
		System.out.println("GanttTaskItem.delegate()");

		// passes info to the first responsible
		Widget parent = ((Widget) this).getParent();
		while (parent != null && parent instanceof Decisionmaker == false) {
			parent = ((Widget) parent).getParent();
		}
		if (parent != null) {
			try {
				((Decisionmaker) parent).decide(action);
			} catch (TBJDException e) {
				e.printStackTrace();
			}
		} else {
			throw new NoUpperDecisionmakerFoundException();
		}
	}

	public void handle(Object action) {
		if (((Map<Integer, ?>) action).containsKey(NotificationsConstants.TASK_PROGRESS_DRAGGING)) {
			setBackgroundWidth((Integer) ((Map<Integer, ?>) action).get(NotificationsConstants.TASK_PROGRESS_DRAGGING));
		}

		if (((Map<Integer, ?>) action).containsKey(NotificationsConstants.TASK_DURATION_DRAGGING)) {
			System.out.println("GanttTaskItem.handle()   = " + ((Map<Integer, ?>) action).get(NotificationsConstants.TASK_DURATION_DRAGGING));
			setManualWidth((Integer) ((Map<Integer, ?>) action).get(NotificationsConstants.TASK_DURATION_DRAGGING));
		}

		if (((Map<Integer, ?>) action).containsKey(NotificationsConstants.TASK_RESOURCES_DRAGGING)) {
			System.out.println("GanttTaskItem.handle() TASK_RESOURCES_DRAGGING ");

			// get the resources (initialsFocusPanels) for this task and set
			// this task's new resources
			setResources(getDisplayedResources());

			if (((Task) getBean()).isDurationModeByRatios()) {
				setComputedWidth();
			}
		}

		if (((Map<Integer, ?>) action).containsKey(NotificationsConstants.DROP_REMOVE_RESOURCEFROMTASK_resource)) {
			System.out.println("GanttTaskItem.handle() DROP_REMOVE_RESOURCEFROMTASK_resource ");

			Resource resource = (Resource) ((Map<Integer, ?>) action).get(NotificationsConstants.DROP_REMOVE_RESOURCEFROMTASK_resource);
			int taskId = (Integer) ((Map<Integer, ?>) action).get(NotificationsConstants.DROP_REMOVE_RESOURCEFROMTASK_taskId);

			removeResource(resource, taskId);
		}
	}

	/**
	 * <p>
	 * Creates the list of associated Resources being displayed
	 * </p>
	 * <p>
	 * This method is called when a Resource has been removed from the GanttTaskItem
	 * </p>
	 * 
	 * @return the list of remaining Resources associated with the GanttTaskItem
	 */
	private List<Resource> getDisplayedResources() {
		// get the number of resources of the capsule
		int resourcesCount = this.resourcesPanel.getWidgetCount();

		// create a list with the remaining resources id's
		List<Integer> newResourcesIds = new ArrayList<Integer>();
		GanttResourceItem resource = null;

		for (int i = 0; i < resourcesCount; i++) {
			resource = (GanttResourceItem) this.resourcesPanel.getWidget(i);
			newResourcesIds.add(resource.getResourceId());
		}

		// create a list with the remaining resources
		List<Resource> newResources = new ArrayList<Resource>();
		for (Integer id : newResourcesIds) {
			for (Resource oldResource : this.resources) {
				if (oldResource.getId() == id) {
					newResources.add(oldResource);
				}
			}
		}

		return newResources;
	}

	/**
	 * <p>
	 * Sets the duration in pixels of the GanttTaskItem
	 * </p>
	 * <p>
	 * This method is called when the user manually changes the GanttTaskItem duration either by editing in the TasksList or by dragging the right
	 * handler of the GanttTaskItem
	 * </p>
	 * 
	 * @param mouseX
	 *            the actual mouse position on the 'x' axis (used to find the new width)
	 */
	private void setManualWidth(int mouseX) {
		System.out.println("GanttTaskItem.setDurationWidth()");
		// Formulas : new width = width + ( mouseX - (width + absLeft) )
		int newWidth = this.capsuleCenterContainer.getOffsetWidth()
				+ (mouseX - (this.capsuleCenterContainer.getOffsetWidth() + this.capsuleCenterContainer.getAbsoluteLeft()));
		if (newWidth < 20) {
			newWidth = 20;
		}
		this.capsuleCenterContainer.setWidth(newWidth + "px");
		this.width = newWidth;

		updateSliderPanel();
	}

	/**
	 * <p>
	 * Computes the duration of the GanttTaskItem in pixels regarding the initial duration of the Task and the associated Resources with their ratios
	 * </p>
	 * <p>
	 * This methods is called when task duration is computed and not manually set by the user by dragging the GanttTaskItem right handle
	 * </p>
	 * <p>
	 * A task is computed when the user sets human/days and task start date in the tasksList or when the associated resources changed
	 * </p>
	 */
	private void setComputedWidth() {
		// Formulas : human days / ( (sum of each resource's ratio) / 100)
		// ex. 30 human/days / ( (3 resources at each 100%) / 100) = 3 effective
		// days
		int ratios = 0;
		if (resources != null && resources.size() > 0) {
			for (Resource resource : resources) {
				ratios += resource.getRatio();
			}
		}

		// if no resources is allocated, the duration is equal to the human/days
		// of the task
		if (ratios < 1) {
			ratios = 100;
		}

		float dividend = (float) ratios / 100;
		Float newWidth = (float) ((Task) getBean()).getDuration() / dividend;
		int newWidthToInt = new Float(newWidth * ZoomConstants.TIMELINE_COLUMN_WIDTH * ZoomConstants.FACTOR).intValue();
		int widthDeduced = newWidthToInt - GanttConstants.GANTTASKTITEM_HANDLEWIDTH * 2;

		this.capsuleCenterContainer.setWidth(widthDeduced + "px");

		// this startupDurationWidth is only used at startup because we need
		// this value to position the progress handle.
		this.startupDurationWidth = widthDeduced;
		this.width = newWidthToInt;

		updateSliderPanel();
	}

	/**
	 * <p>
	 * Sets the width in pixels of the progress background
	 * </p>
	 * <p>
	 * This method is called when the progress slider has changed
	 * </p>
	 * 
	 * @param mouseX
	 *            the position in pixels of the mouse pointer on the 'x' axis
	 */
	private void setBackgroundWidth(int mouseX) {
		System.out.println("GanttTaskItem.setBackgroundWidth(" + mouseX + ")");
		// Get the progress handle position in percentage and apply this to the
		// capsuleCenterBackground width :
		int handlePosition = sliderPanel.getWidgetLeft(sliderPanel.getWidget(0));
		if (handlePosition < 1) {
			handlePosition = 0;
		}
		if (handlePosition > sliderPanel.getOffsetWidth()) {
			handlePosition = sliderPanel.getOffsetWidth() - 1;
		}
		this.capsuleCenterBackground.setWidth(handlePosition * 100 / sliderPanel.getOffsetWidth() + "%");
	}

	/**
	 * <p>
	 * Sets the slider width according to the width of the GanttTaskItem as well as the slider handle position aligned with the progress background
	 * </p>
	 * <p>
	 * This method is called when the width of the GanttTaskItem changed
	 * </p>
	 */
	private void updateSliderPanel() {
		// FIXME slider positionning
		// update the width of the sliderPanel
		// formulas : capsuleCenterContainer.getOffsetWidth() + sliderPanelHandle.getOffsetWidth()
		// this.sliderPanel.setWidth("px");

		// then update the position of the progress handle
		this.sliderPanel.setWidgetPosition(this.sliderPanelHandle, this.capsuleCenterBackground.getOffsetWidth(), 0);
	}

	/**
	 * This method displays Resources associated with the GanttTaskItem
	 */
	private void displayResources() {
		GanttResourceItem resourceItem;
		if (this.resources != null) {
			for (Resource resource : this.resources) {
				resourceItem = new GanttResourceItem();
				resourceItem.setHeight(GanttConstants.DRAGGABLEGANTTITEM_HEIGHT + "px");
				resourceItem.setTaskId(((Task) getBean()).getId());
				resourceItem.setResource(resource);
				this.resourcesPanel.add(resourceItem);
				this.resourceDragController.makeDraggable(resourceItem, resourceItem.getHandle());
			}
		}
	}

	public void addResource(Resource resource) {
		GanttResourceItem resourceItem = new GanttResourceItem();
		resourceItem.setHeight(GanttConstants.DRAGGABLEGANTTITEM_HEIGHT + "px");
		resourceItem.setTaskId(((Task) getBean()).getId());
		resourceItem.setResource(resource);
		this.resourcesPanel.add(resourceItem);
		this.resourceDragController.makeDraggable(resourceItem, resourceItem.getHandle());
		if(this.resources == null){
			this.resources = new ArrayList<Resource>();
		}
		this.resources.add(resource);
		setComputedWidth();
	}

	/**
	 * When a Resource is removed from a Task, this method updates both Resource and Task relationship data
	 * 
	 * @param resource
	 *            the Resource removed from the Task
	 * @param taskId
	 *            the taskId contained in the removed Resource
	 */
	public void removeResource(Resource resource, int taskId) {
		// remove Resource bean from resource list
		this.resources.remove(resource);

		// compute the new duration of the task
		setComputedWidth();
	}

	public void addPredecessor(Integer predecessor) {
		this.predecessors.add(predecessor);
	}

	public void addSuccessor(Integer successor) {
		this.successors.add(successor);
	}

	public void removePredecessor(Integer predecessor) {
		this.predecessors.remove(predecessor);
	}

	public void removeSuccessor(Integer successor) {
		this.successors.remove(successor);
	}

	public void setLabel(FocusPanel handle) {
		this.capsuleCenterForeground = handle;
	}

	public FocusPanel getHandle() {
		return capsuleCenterForeground;
	}

	public Widget getProgressHandle() {
		return this.sliderPanelHandle;
	}

	public AbsolutePanel getProgressPanel() {
		return sliderPanel;
	}

	public FocusPanel getDurationHandle() {
		return rightHandle;
	}

	public AbsolutePanel getDurationPanel() {
		return rightHandlePanel;
	}

	public void setResources(List<Resource> resources) {
		this.resources = resources;
	}

	public List<Resource> getResources() {
		return resources;
	}

	public Widget getBuildedWidget() {
		return this.container;
	}

	public void setLabel(String label) {
		this.label.setText(label);
	}

	public int getWidth() {
		return width;
	}

	public void setWidth(int width) {
		this.width = width;
	}

}
